package cn.itcast.nems.enrol.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.bean.BizError;
import cn.itcast.ic.common.exception.BizException;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.base.api.BusinessLineApi;
import cn.itcast.nems.base.api.OriginChannelApi;
import cn.itcast.nems.base.api.PaymentTypeApi;
import cn.itcast.nems.base.dao.mapper.PreStudentInfoMapper;
import cn.itcast.nems.base.dto.BusinessLineDTO;
import cn.itcast.nems.base.dto.EntityExtendAttributeDTO;
import cn.itcast.nems.base.dto.OriginChannelDTO;
import cn.itcast.nems.base.util.ProductBeanUtil;
import cn.itcast.nems.common.constant.DiscountScopeTypeEnum;
import cn.itcast.nems.common.constant.OrderStateEnum;
import cn.itcast.nems.common.enumeration.CustomBizError;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.util.LocalDateTimeUtils;
import cn.itcast.nems.common.util.NumberUtil;
import cn.itcast.nems.common.utils.WrapperUtil;
import cn.itcast.nems.common.validator.CustomerValidator;
import cn.itcast.nems.enrol.api.EnrolApi;
import cn.itcast.nems.enrol.dto.*;
import cn.itcast.nems.enrol.enumeration.EnrolErrorBodyEnum;
import cn.itcast.nems.enrol.service.EnrolService;
import cn.itcast.nems.manager.ems.EmsManger;
import cn.itcast.nems.manager.kc.KeyCloakManager;
import cn.itcast.nems.manager.kc.model.AccessTokenModel;
import cn.itcast.nems.manager.redis.RedisUtil;
import cn.itcast.nems.manager.redis.constant.RedisKeyConstant;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.order.api.JournalDiscountApi;
import cn.itcast.nems.order.api.OrderApi;
import cn.itcast.nems.order.dao.dataobject.EnrolPaidOrderDO;
import cn.itcast.nems.order.dao.dataobject.OrderLineDO;
import cn.itcast.nems.order.dao.dataobject.OrderLinkDO;
import cn.itcast.nems.order.dao.entity.Order;
import cn.itcast.nems.order.dao.entity.OrderDiscount;
import cn.itcast.nems.order.dao.entity.OrderExtend;
import cn.itcast.nems.order.dao.mapper.OrderDiscountMapper;
import cn.itcast.nems.order.dao.mapper.OrderMapper;
import cn.itcast.nems.order.dto.*;
import cn.itcast.nems.order.service.OrderExtendService;
import cn.itcast.nems.order.service.OrderLineService;
import cn.itcast.nems.order.service.OrderService;
import cn.itcast.nems.product.dao.dataobject.BizNo;
import cn.itcast.nems.product.dao.dataobject.ProductDO;
import cn.itcast.nems.product.dao.entity.Product;
import cn.itcast.nems.product.dao.mapper.ProductMapper;
import cn.itcast.nems.studentrecord.constant.OrderSourceEnum;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
@RequiredArgsConstructor
public class EnrolV2ServiceImpl implements EnrolService, EnrolApi {

    @Value("${ems2.nems-web.url}")
    private String nemsWebUrl;
    @Value("${ems2.keycloak.o-api.cloud-client-id}")
    private String sysClientId;
    @Value("${ems.enrol.url}")
    private String emsEnrolUrl;

    private final OrderExtendService orderExtendService ;
    private final OrderService orderService ;
    private final OrderLineService orderLineService ;
    private final OrderApi orderApi;
    private final OrderDiscountMapper orderDiscountMapper;
    private final PaymentTypeApi paymentTypeApi;
    private final OriginChannelApi originChannelApi;
    private final BusinessLineApi businessLineApi;
    private final ProductMapper productMapper;
    private final OrderMapper orderMapper;
    private final PreStudentInfoMapper preStudentInfoMapper;
    private final RedisUtil redisUtil;
    private final KeyCloakManager keyCloakManager;
    private final EmsManger emsManger;
    private final JournalDiscountApi journalDiscountApi;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public EnrolFormDTO confirmStudentInfo(EnrolFormDTO formDTO) {
        // 获取缓存中 [scrm] 点击立即报名传的报名信息
        EnrolCreateVO createVO;
        if(formDTO instanceof EnrolQuickFormDTO quickFormDTO){
            createVO = quickFormDTO.getCreateVO();
        }else {
            createVO = this.getEnrolCreateVO(formDTO.getSid());
        }
        EnrolOrderDTO orderDTO = this.transferEnrolOrderDTO(formDTO, createVO);
        formDTO.setOrder(orderDTO);
        // 已完成缴费信息
        List<EnrolPaidOrderDO> paidOrderList = orderMapper.findEnrolPaidOrderList(new EnrolPaidOrderDO(String.valueOf(createVO.getMdid())));
        if (CollectionUtil.isNotEmpty(paidOrderList)) {
            boolean existsSameClazz =  paidOrderList.stream()
                    .filter(o -> OrderStateEnum.PAID.name().equals(o.getOrderState()))
                    .anyMatch(o -> {
                        BizNo bizNo = new BizNo(o.getBizNo());
                        return orderDTO.getClassId().equals(bizNo.getClazzId());
                    });
            Assert.isFalse(existsSameClazz, BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_SAME_CLAZZ_ERROR));
        }
        String data = JSON.toJSONString(formDTO, CustomBeanUtil.SERIALIZE_CONFIG);
        // 校验并取消历史待支付订单
        this.checkAndCancelOrder(orderDTO.getOrderNo(), null);
        // 校验优惠单
        this.checkJournalDiscount(orderDTO);
        log.info("[确认报名] 调用EMS报名请求参数: {}", data);
        try {
            // 调用  EMS 确认报名接口
            data = emsManger.doPost(data, createVO.getOperationEmail(), "/student.do", "addStudent");
            log.info("[确认报名] 调用EMS报名响应正文: {}", data);
        } catch (Exception e) {
            log.warn("[确认报名] 调用EMS报名异常：{}" , e.getMessage());

            BizExceptionProducer.throwProduce(e,EnrolErrorBodyEnum.EMS_REQUEST_ERROR);
        }
        // EmsEnrolFormDTO emsEnrolFormDTO = JSON.parseObject(data, EmsEnrolFormDTO.class);
        EMSEnrolResponseDTO responseDTO = JSON.parseObject(data, EMSEnrolResponseDTO.class);
        Assert.notNull(responseDTO, BizExceptionProducer.produce(EnrolErrorBodyEnum.EMS_REQUEST_ERROR));
        Assert.isTrue(responseDTO.getSuccess(), BizExceptionProducer.produce(EnrolErrorBodyEnum.EMS_ENROL_FAIL , responseDTO.getErrorMessage()));
        EmsEnrolFormDTO emsEnrolFormDTO = responseDTO .getData() ;
        Assert.isTrue(CollectionUtil.isNotEmpty(emsEnrolFormDTO.getOrderList()), BizExceptionProducer.produce(EnrolErrorBodyEnum.EMS_RESPONSE_ORDER_NOT_FUND));
        Assert.isTrue(Objects.nonNull(orderDTO.getCustomer()) && Objects.nonNull(orderDTO.getCustomer().getMdid()), BizExceptionProducer.produce(EnrolErrorBodyEnum.EMS_RESPONSE_CUSTOMER_NOT_FUND));
        Assert.notNull(emsEnrolFormDTO.getSjcId(), BizExceptionProducer.produce(EnrolErrorBodyEnum.EMS_PARAM_EMPTY ,"学籍ID")) ;
        // 获取第一阶段订单 ,为了使多阶段关联订单ID
        EnrolOrderDTO payUrlOrderDTO = emsEnrolFormDTO.getOrderList().stream()
                .filter(item -> StringUtils.hasText(item.getPayUrl()))
                .findFirst()
                .orElseThrow(BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_DETAIL_LINK_NOT_FUND));
        List<CreateOrderResultDTO> resultList = new ArrayList<>();
        NewOrderDTO firstNewOrderDTO = this.transferNewOrderDTO(new NewOrderDTO(), payUrlOrderDTO, null, emsEnrolFormDTO.getSjcId());
        firstNewOrderDTO.setSource(OrderSourceEnum.NEW.name());
        log.info("[确认报名]调用 oder api 订单参数: {}", JSON.toJSONString(firstNewOrderDTO));
        final CreateOrderResultDTO orderResult = orderApi.create(firstNewOrderDTO);
        log.info("[确认报名] 创建第一阶段订单, 结果: {}", JSON.toJSONString(orderResult));
        resultList.add(orderResult);
        resultList.addAll(emsEnrolFormDTO.getOrderList().stream()
                .filter(item -> new CustomerValidator<EnrolOrderDTO>()
                        .with(i -> !payUrlOrderDTO.getBizOrderNo().equalsIgnoreCase(i.getBizOrderNo())).validate(item)).map(item -> {
                    NewOrderDTO newOrderDTO = this.transferNewOrderDTO(new NewOrderDTO(), item, orderResult.getOrderId(), emsEnrolFormDTO.getSjcId());
                    newOrderDTO.setSource(OrderSourceEnum.NEW.name());
                    log.info("[确认报名]调用 oder api 订单参数: {}", JSON.toJSONString(newOrderDTO));
                    return orderApi.create(newOrderDTO);
                }).toList());
        log.info("[确认报名] 创建订单, 结果: {}", JSON.toJSONString(resultList));
        orderDTO.setPayUrl(payUrlOrderDTO.getPayUrl());
        orderDTO.setOrderNo(orderResult.getOrderNo());
        orderDTO.setState(orderResult.getState());
        formDTO.setOrder(orderDTO);
        return formDTO;
    }

    private EnrolOrderDTO transferEnrolOrderDTO(EnrolFormDTO formDTO, EnrolCreateVO createVO) {
        EnrolOrderDTO orderDTO = formDTO.getOrder();
        // 获取客户信息
        EnrolCustomerDTO emsEnrolCustomerDTO = CustomBeanUtil.copyProperties(createVO, new EnrolCustomerDTO());
        // 业务线
        orderDTO.setBusinessLineCode(createVO.getBusinessLineCode());
        // 订单来源
        orderDTO.setOriginChannelCode(createVO.getOriginChannelCode());
        // 固定discount 顺序
        if (CollectionUtil.isNotEmpty(orderDTO.getOrderLineList())) {
            for (int i = 0; i < orderDTO.getDiscountList().size(); i++) {
                orderDTO.getDiscountList().get(i).setSort((byte) (i + 1));
            }
            if (CollectionUtil.isNotEmpty(orderDTO.getPersonalDiscountList())) {
                for (int i = 0; i < orderDTO.getPersonalDiscountList().size(); i++) {
                    orderDTO.getPersonalDiscountList().get(i).setSort((byte) (i + 1));
                }
            }
        }
        // 是否享受院校优惠券
        orderDTO.setIsacademy(false);
        orderDTO.setCustomer(emsEnrolCustomerDTO);
        return orderDTO;
    }

    @Override
    public List<CreateOrderResultDTO> callback(List<NewOrderDTO> orderList, List<String> bizNoList) {
        List<CreateOrderResultDTO> resultList = new ArrayList<>();
        NewOrderDTO firstNewOrderDTO = orderList.stream()
                .filter(item -> StringUtils.hasText(item.getPayUrl()))
                .findFirst()
                .orElseThrow(BizExceptionProducer.produce(EnrolErrorBodyEnum.NO_EXIST, "支付链接订单"));
        firstNewOrderDTO.setSource(OrderSourceEnum.NEW.name());
        log.info("[EMS报名回调接口]调用 oder api 订单参数: {}", JSON.toJSONString(firstNewOrderDTO));
        // 校验并取消历史待支付订单
        this.checkAndCancelOrder(null, bizNoList);
        final CreateOrderResultDTO orderResul = orderApi.create(firstNewOrderDTO);
        log.info("[EMS报名回调接口] 创建第一阶段订单, 结果: {}", JSON.toJSONString(orderResul));
        resultList.add(orderResul);
        resultList.addAll(orderList.stream()
                .filter(item -> new CustomerValidator<NewOrderDTO>()
                        .with(i -> !firstNewOrderDTO.getBizOrderNo().equalsIgnoreCase(i.getBizOrderNo()))
                        .validate(item)).map(item -> {
                    item.setAssociatedOrderId(orderResul.getOrderId());
                    item.setSource(OrderSourceEnum.NEW.name());
                    log.info("[EMS报名回调接口]调用 oder api 订单参数: {}", JSON.toJSONString(item));
                    return orderApi.create(item);
                }).toList());
        log.info("[EMS报名回调接口] 创建订单, 结果: {}", JSON.toJSONString(resultList));
        return resultList;
    }

    /**
     * 转化订单头
     * @param orderDTO 公共内部接口DTO
     * @param enrolOrderDTO EMS 返回订单头
     * @param associatedOrderId 关联订单ID
     * @return 订单头DTO
     */
    private NewOrderDTO transferNewOrderDTO(NewOrderDTO orderDTO,EnrolOrderDTO enrolOrderDTO ,String associatedOrderId ,Integer joinClassId) {
        log.info("[确认报名]解析订单头: {}", JSON.toJSONString(enrolOrderDTO));
        orderDTO.setBizOrderNo(enrolOrderDTO.getBizOrderNo());
        orderDTO.setAssociatedOrderId(associatedOrderId);
        orderDTO.setBizDate(LocalDateTime.now());
        orderDTO.setCreatedBy(enrolOrderDTO.getCustomer().getOperationEmail());
        orderDTO.setCurrency("CNY");
        orderDTO.setPayUrl(enrolOrderDTO.getPayUrl());
        orderDTO.setJoinClassId(joinClassId);
        // 维护主数据
        orderService.findAndInvokeBaseData(orderDTO ,enrolOrderDTO.getPaymentTypeCode() ,
                enrolOrderDTO.getBusinessLineCode(), enrolOrderDTO.getOriginChannelCode());
        // 转化订单行
        List<NewOrderLineDTO> orderLines = enrolOrderDTO.getOrderLineList().stream().
                map(item -> this.transferNewOrderLineDTO(new NewOrderLineDTO(), item, enrolOrderDTO.getClassId(), enrolOrderDTO.getPhaseNumber()))
                .collect(Collectors.toList());
        orderDTO.setLines(orderLines) ;
        // 转化订单头优惠项
        List<NewOrderDiscountDTO> discountList = enrolOrderDTO.getDiscountList().stream()
                .map(item-> this.transferNewOrderDiscountDTO(
                        new NewOrderDiscountDTO(),item, orderLines.get(0).getProductId(),orderLines.get(0).getProductBizNo()))
                .toList();
        orderDTO.setDiscounts(discountList);
        // 转化订单头优惠单
        List<NewOrderDiscountDTO> personalDiscountList = enrolOrderDTO.getPersonalDiscountList().stream()
                .map(item-> this.transferNewOrderDiscountDTO(
                        new NewOrderDiscountDTO(),item, orderLines.get(0).getProductId(),orderLines.get(0).getProductBizNo()))
                .toList();
        orderDTO.setPersonalDiscounts(personalDiscountList);
        // 订单扩展属性
        List<EntityExtendAttributeDTO> extendList = this.transferEntityExtendAttributeDTO(enrolOrderDTO) ;
        orderDTO.setExtendAttributes(extendList);
        // 订单客户关系
        NewOrderCustomerRelationshipDTO shipDTO = this.transferNewOrderCustomerRelationshipDTO(
                new NewOrderCustomerRelationshipDTO(),enrolOrderDTO.getCustomer()) ;
        orderDTO.setOrderCustomerRelationship(shipDTO);
        orderDTO.setTheFreshDate(enrolOrderDTO.getTheFreshDate());
        orderDTO.setIsJoinClass(enrolOrderDTO.getIsJoinClass());
        // 需要验证产品价格
        orderDTO.setIsValidatePrice(true);
        return orderDTO ;
    }

    /**
     * 转化 订单行
     * @param orderLineDTO 订单行DTO
     * @param enrolOrderLineDTO 订单行DTO
     * @return 调用订单API  BEAN
     */
    private NewOrderLineDTO transferNewOrderLineDTO(NewOrderLineDTO orderLineDTO , EnrolOrderLineDTO enrolOrderLineDTO,
                                                    Integer classId ,Integer phaseNumber) {
        // 匹配 产品ID
        log.info("[确认报名]解析并转化订单行: {}", JSON.toJSONString(enrolOrderLineDTO));
        //  TODO 需要将产品表中 校区、授课模式、学科和班级类型与页面提交表单进行对比,  避免出现不一致导致显示详情出问题
        List<ProductDO> productDOList = productMapper.selectValidProductsByIdOrLikeBizNo("C-" + classId,
                StringUtils.hasText(orderLineDTO.getProductId()) ?Long.valueOf(orderLineDTO.getProductId()) : null, phaseNumber);
        Assert.isTrue(CollectionUtil.isNotEmpty(productDOList), BizExceptionProducer.produce(EnrolErrorBodyEnum.PRODUCT_NOT_FUND));
        // 匹配产品表ID  ,  后续需要核对产品价格和EMS回传的订单价格
        orderLineDTO.setProductId(Objects.nonNull(productDOList.get(0).getId())? String.valueOf(productDOList.get(0).getId()) :null);
        orderLineDTO.setProductBizNo(productDOList.get(0).getBizNo());
        orderLineDTO.setAmountDue(NumberUtil.convertToCent(enrolOrderLineDTO.getAmountDue()));
        orderLineDTO.setDiscountAmount(NumberUtil.convertToCent(enrolOrderLineDTO.getDiscountAccount()));
        orderLineDTO.setPrice(NumberUtil.convertToCent(enrolOrderLineDTO.getPrice()));
        orderLineDTO.setAlreadyLearnFee(NumberUtil.convertToCent(enrolOrderLineDTO.getAlreadyLearnFee()));
        // 只有第一阶段订单维护首次支付金额
        orderLineDTO.setFirstPayment(0);
        if (Objects.isNull(phaseNumber) || phaseNumber == 1) {
            orderLineDTO.setFirstPayment(NumberUtil.convertToCent(enrolOrderLineDTO.getFirstPayment()));
        }
        enrolOrderLineDTO.setQuantity(Objects.isNull(enrolOrderLineDTO.getQuantity()) ? 1 : enrolOrderLineDTO.getQuantity());
        orderLineDTO.setQuantity(Integer.valueOf(enrolOrderLineDTO.getQuantity()));
        orderLineDTO.setOriginalAmount(orderLineDTO.getPrice() * enrolOrderLineDTO.getQuantity() );
        return orderLineDTO ;
    }

    /**
     * 转化优惠项
     * @param discountDTO
     * @param enrolDiscountDTO
     * @param productId
     * @param ProductBizNo
     * @return NewOrderDiscountDTO
     */
    private NewOrderDiscountDTO transferNewOrderDiscountDTO(NewOrderDiscountDTO discountDTO ,
                                                            EnrolDiscountDTO enrolDiscountDTO,String productId, String ProductBizNo ) {
        log.info("[确认报名]解析并转化优惠项: {}", JSON.toJSONString(enrolDiscountDTO));
        discountDTO.setDiscountId(String.valueOf(enrolDiscountDTO.getDiscountId()));
        discountDTO.setDiscountAmount(NumberUtil.convertToCent(enrolDiscountDTO.getDiscountMoney()));
        discountDTO.setJournalDiscountLineId(enrolDiscountDTO.getJournalDiscountLineId());
        discountDTO.setSort(enrolDiscountDTO.getSort());
        discountDTO.setProductId(productId);
        discountDTO.setProductBizNo(ProductBizNo);
        return discountDTO ;
    }

    /**
     * 转化订单客户关系
     * @param shipDTO
     * @param customerDTO
     * @return
     */
    private NewOrderCustomerRelationshipDTO transferNewOrderCustomerRelationshipDTO (NewOrderCustomerRelationshipDTO shipDTO,
                                                                                     EnrolCustomerDTO customerDTO) {
        log.info("[确认报名]解析并转化订单头客户关系: {}", JSON.toJSONString(customerDTO));
        shipDTO.setCustomerId(String.valueOf(customerDTO.getMdid()));
        shipDTO.setCustomerQq(customerDTO.getQq());
        shipDTO.setCustomerGender(customerDTO.getGender());
        shipDTO.setCustomerMobile(customerDTO.getPhone());
        shipDTO.setCustomerName(customerDTO.getName());
        shipDTO.setCustomerWechat(customerDTO.getWechat());
        shipDTO.setCounselorEmail(customerDTO.getBelongerEmail());
        shipDTO.setCounselorId(Objects.isNull(customerDTO.getBelongerId()) ? null:String.valueOf(customerDTO.getBelongerId()));
        shipDTO.setCounselorTeam(customerDTO.getBelongerDepartment());
        shipDTO.setCounselorName(customerDTO.getBelongerName());

        shipDTO.setOperatorEmail(customerDTO.getOperationEmail());
        shipDTO.setOperatorName(customerDTO.getOperationName());
        return shipDTO ;
    }

    /**
     * 转化订单头拓展字段
     * @param enrolOrderDTO 订单头
     * @return 订单头拓展字段DTO
     */
    private List<EntityExtendAttributeDTO> transferEntityExtendAttributeDTO(EnrolOrderDTO enrolOrderDTO) {
        List<EntityExtendAttributeDTO> list = new ArrayList<>();
        try {
            Map<String, Object> map = CustomBeanUtil.objectAttributeMap(enrolOrderDTO, "isJoinClass", "jobFee", "jobTeachingModel", "theFreshDate");
            for (Map.Entry<String, Object> entry : map.entrySet()) {
                String k = entry.getKey();
                Object v = entry.getValue();
                if (Objects.nonNull(v)) {
                    EntityExtendAttributeDTO attributeDTO = new EntityExtendAttributeDTO();
                    attributeDTO.setName(k);
                    String value;
                    if (v instanceof LocalDate) {
                        value = LocalDateTimeUtils.formatLocalDate((LocalDate) v);
                    } else if (v instanceof LocalDateTime) {
                        value = LocalDateTimeUtils.formatLocalDateTime((LocalDateTime) v);
                    } else {
                        value = String.valueOf(v);
                    }
                    attributeDTO.setValue(value);
                    list.add(attributeDTO);
                }
            }
            return list;
        } catch (IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            log.info("[报名页面]新增订单头拓展信息失败,  异常: {}", e.getMessage());
            throw new BizException(CustomBizError.message(BizError.BIZ_DATA_EXCEPTION, "参数异常"));
        }

    }

    /**
     * 校验优惠单是否可以使用
     *
     * @param orderDTO 订单详情
     * @author 高昌露
     * @since 2023/12/14 14:54
     */
    public synchronized void checkJournalDiscount(@NonNull EnrolOrderDTO orderDTO) {
        if (CollectionUtil.isNotEmpty(orderDTO.getPersonalDiscountList())) {
            orderDTO.getPersonalDiscountList().forEach(d -> Assert.isTrue(
                    journalDiscountApi.validityVerify(
                            JournalDiscountApiReqDTO.buildForWriteOff(
                                    d.getJournalDiscountLineId(),
                                    null,
                                    orderDTO.getCustomer().getMdid().toString(),
                                    orderDTO.getClassId(),
                                    LocalDateTime.now(),
                                    JournalDiscountApiReqDTO.Scenario.EMPLOYEE_SIGN_UP
                            )
                    ),
                    BizExceptionProducer.produce(EnrolErrorBodyEnum.JOURNAL_DISCOUNT_ERROR, d.getDiscountName())
            ));
        }
    }

    /**
     * 检查当前客户ID是否存在待支付的订单, 如果存在, 将对应订单状态设置为取消操作
     *
     * @param orderNo NEMS 订单号
     * @param  historyBizNoList 历史业务编号
     */
    public synchronized void checkAndCancelOrder(String orderNo, List<String> historyBizNoList) {
        log.info("[取消历史待支付订单]需要处理的历史业务订单号: {}",  JSON.toJSON(historyBizNoList));
        // 新EMS编辑操作
        if (StringUtils.hasText(orderNo)) {
            LambdaQueryWrapper<Order> queryWrapper = new WrapperUtil<Order>().limitLaseOne("created_datetime").eq(Order::getOrderNo, orderNo);
            Order order = orderMapper.selectOne(queryWrapper);
            Assert.notNull(order, BizExceptionProducer.produce(EnrolErrorBodyEnum.NO_EXIST, orderNo));
            // 订单状态只有为 “待支付” ,才允许修改
            if (!OrderStateEnum.PENDING.name().equalsIgnoreCase(order.getState())) {
                Assert.notNull(order,  BizExceptionProducer.produce(EnrolErrorBodyEnum.ORDER_UPDATE_STATE_ERROR, orderNo, order.getState()));
            }
            // 检查关联订单是否有 部分支付、完全支付的情况
            List<Order> orderList =  orderMapper.selectList(new LambdaQueryWrapper<Order>()
                    .and(qw -> qw.eq(Order::getId, order.getId()).or().eq(Order::getAssociatedOrderId, order.getId()))
                    .in(Order::getState, OrderStateEnum.PAYING.name(),OrderStateEnum.PAID.name())) ;
            log.info("[取消历史待支付订单] 订单编号: {} ,查询结果: {}", orderNo, JSON.toJSON(orderList));
            Assert.isTrue(CollectionUtil.isEmpty(orderList), BizExceptionProducer.produce(EnrolErrorBodyEnum.ORDER_PAID,order));
            int updateFLag = orderMapper.update(null, new LambdaUpdateWrapper<Order>()
                    .and(qw -> qw.eq(Order::getId, order.getId()).or().eq(Order::getAssociatedOrderId, order.getId()))
                    .set(Order::getState, OrderStateEnum.CANCELED.name()));
            Assert.isTrue(updateFLag > 0, BizExceptionProducer.produce(EnrolErrorBodyEnum.ORDER_CANCEL_ERROR, orderNo));
            log.info("[取消历史待支付订单] 投递取消订单消息 订单信息: {}", JSON.toJSONString(order));
            orderService.sendMessageAfterCanceled(order);
        }
        // 老EMS编辑操作
        if (CollectionUtil.isNotEmpty(historyBizNoList)) {
            String bizNos = JSON.toJSONString(historyBizNoList) ;
            List<Order> orderList = orderMapper.selectList(new LambdaQueryWrapper<Order>()
                    .in(Order::getBizOrderNo, historyBizNoList)
                    .in(Order::getState, OrderStateEnum.PENDING.name(), OrderStateEnum.CANCELED.name()));
            log.info("[取消历史待支付订单] 业务编号: {} ,查询结果: {}", JSON.toJSONString(historyBizNoList),bizNos);
            Assert.isTrue(!orderList.isEmpty() && orderList.size() >= historyBizNoList.size(),
                    BizExceptionProducer.produce(EnrolErrorBodyEnum.NO_EXIST, JSON.toJSONString(historyBizNoList)));

            List<Order> needCancelOrders = orderList
                    .stream()
                    .filter(item -> OrderStateEnum.PENDING.name().equals(item.getState()))
                    .toList();
            if (!needCancelOrders.isEmpty()) {
                List<String> needCancelOrderIds = needCancelOrders.stream().map(Order::getId).toList();
                int updateFLag = orderMapper.update(null, new LambdaUpdateWrapper<Order>()
                        .in(Order::getId, needCancelOrderIds)
                        .set(Order::getState, OrderStateEnum.CANCELED.name()));
                Assert.isTrue(updateFLag > 0, BizExceptionProducer.produce(EnrolErrorBodyEnum.ORDER_CANCEL_ERROR, bizNos));

                log.info("[取消历史待支付订单] 投递取消订单消息 订单列表: {}", JSON.toJSONString(needCancelOrders));
                needCancelOrders.forEach(orderService::sendMessageAfterCanceled);
            }
        }
    }

    @Override
    public String url(EnrolCreateVO createVO) {
        // 是否通过新NEMS进行报名
        if (!this.isForwardNems(createVO)) {
            return emsEnrolUrl + "&hasEntryClassRisk=" + createVO.isHasEntryClassRisk() + "&cid=" + createVO.getCid();
        }
        // 业务线
        Assert.notBlank(createVO.getBusinessLineCode(), BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "业务线"));
        List<BusinessLineDTO> businessLineList = !StringUtils.hasText(createVO.getBusinessLineCode()) ? null :
                Optional.of(businessLineApi.findByParam(new BusinessLineDTO(createVO.getBusinessLineCode()))).orElseThrow(
                        BizExceptionProducer.produce(EnrolErrorBodyEnum.BUSINESS_LINE_NOT_FUND, createVO.getBusinessLineCode()));
        log.info("[生成报名url] business_line_code: {}, 查询结果: {}", createVO.getBusinessLineCode(), JSON.toJSONString(businessLineList));
        // 订单来源
        Assert.notBlank(createVO.getBusinessLineCode(), BizExceptionProducer.produce(NemsErrorBodyEnum.PARAM_EMPTY, "订单来源"));
        List<OriginChannelDTO> originChannelList = !StringUtils.hasText(createVO.getOriginChannelCode()) ? null :
                Optional.of(originChannelApi.findByParam(new OriginChannelDTO(createVO.getOriginChannelCode()))).orElseThrow(
                        BizExceptionProducer.produce(EnrolErrorBodyEnum.ORIGIN_CHANNEL_NOT_FUND, createVO.getOriginChannelCode()));
        log.info("[生成报名url] origin_channel_code: {}, 查询结果: {}", createVO.getOriginChannelCode(), JSON.toJSONString(originChannelList));
        String sid = UUID.randomUUID().toString().replace("-", "");
        String key = CustomBeanUtil.append("|", RedisKeyConstant.REDIS_ENROL_CREATE, sid);
        log.debug("[生成报名页面链接]  sid: {},redis_key: {}", sid, key);
        try {
            redisUtil.set(key, createVO, 60 * 60);
            log.info("缓存是否存在: {} ,缓存数据: {}", redisUtil.hasKey(key), JSON.toJSONString(redisUtil.get(key)));
        } catch (Exception e) {
            BizExceptionProducer.throwProduce(e, EnrolErrorBodyEnum.REDIS_ERROR);
        }
        return nemsWebUrl + "?sid=" + sid;
    }

    /**
     * 是否通过新NEMS进行报名
     *
     * @param createVO SCRM 请求参数
     * @return boolean
     */
    private Boolean isForwardNems(EnrolCreateVO createVO) {
        //        // 根据客户ID判断是否存在历史报名订单  , 如果存在跳转EMS报名页面
        //        if (Objects.nonNull(createVO.getCid())) {
        //            // 根据 cid 和订单状态 “待支付” 查询订单头
        //            OrderLinkDO orderLinkDO = orderMapper.findPendingList(String.valueOf(createVO.getMdid()));
        //            if (Objects.nonNull(orderLinkDO)) {
        //                return true;
        //            }
        //            List<PreStudentInfo> preStudentInfoList = preStudentInfoMapper.selectList(
        //                    new LambdaQueryWrapper<PreStudentInfo>().eq(PreStudentInfo::getCustomerId, createVO.getCid()).eq(PreStudentInfo::getStatus, 0));
        //            if (CollectionUtil.isNotEmpty(preStudentInfoList)) {
        //                log.info("[确认报名] EMS存在报名订单: {}", JSON.toJSONString(preStudentInfoList));
        //                return false;
        //            }
        //        }
        return true;
    }

    @Override
    public AccessTokenModel<?> getToken(String sid) {
        // 检查sid 这个key在缓存中是否存在
        String key = CustomBeanUtil.append("|", RedisKeyConstant.REDIS_ENROL_CREATE, sid);
        boolean isExist = redisUtil.hasKey(key);
        Assert.isTrue(isExist, BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_URL_EXPIRE));
        String token = null;
        try {
            token = keyCloakManager.getKeyCloakAccessToken();
        } catch (Exception e) {
            BizExceptionProducer.throwProduce(e, EnrolErrorBodyEnum.TOKEN_OBTAIN_ERROR);
        }
        return new AccessTokenModel<>(token, sysClientId, redisUtil.get(key));
    }


    public EnrolCreateVO getEnrolCreateVO(String sid) {
        Assert.notNull(sid, BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_URL_EXPIRE));
        String key = CustomBeanUtil.append("|", RedisKeyConstant.REDIS_ENROL_CREATE, sid);
        Assert.isTrue(redisUtil.hasKey(key), BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_URL_EXPIRE));
        return (EnrolCreateVO) redisUtil.get(key);
    }

    @Override
    public Object getPayInfo(Object param) {
        return null;
    }

    @Override
    public EnrolFormDTO getEnrolInfo(EnrolQueryDTO queryDTO) {
        EnrolFormDTO formDTO = new EnrolFormDTO(queryDTO.getSid());
        EnrolCreateVO createVO = this.getEnrolCreateVO(queryDTO.getSid());
        EnrolCustomerDTO customerDTO = CustomBeanUtil.copyProperties(createVO, new EnrolCustomerDTO());
        // 已完成缴费信息
        List<EnrolPaidOrderDO> paidOrderList = orderMapper.findEnrolPaidOrderList(new EnrolPaidOrderDO(String.valueOf(customerDTO.getMdid())));
        formDTO.setPaidList(CollectionUtil.isEmpty(paidOrderList) ? null :
                paidOrderList.stream().map(item -> CustomBeanUtil.copyProperties(item, new EnrolPaidOrderDTO())).collect(Collectors.toList()));
        // 根据 cid 和订单状态 “待支付” 查询订单头 -- 需要考虑一个班级有多个阶段 , 有可能第一阶段订单状态为部分支付或已支付
        OrderLinkDO orderLinkDO = orderMapper.findPendingList(String.valueOf(customerDTO.getMdid()));
        // 如果没有获取到待支付的订单信息(e2_order_enrollment_link),  返回空的订单头信息 , 页面没有获取到订单号, 则进行新增报名操作
        EnrolOrderDTO orderDTO = null;
        if (Objects.isNull(orderLinkDO)) {
            orderDTO = new EnrolOrderDTO();
            orderDTO.setCustomer(customerDTO);
            formDTO.setOrder(orderDTO);
            return formDTO;
        }
        List<Order> orderList = orderMapper.selectList(new LambdaQueryWrapper<Order>()
                .and(qr -> qr.eq(Order::getId, orderLinkDO.getId())).or().eq(Order::getAssociatedOrderId, orderLinkDO.getId()));
        // 查询 e2_order_enrollment_link 有支付链接的订单
        List<String> orderIds = orderList.stream().map(Order::getId).collect(Collectors.toList());
        Order order = orderMapper.selectById(orderLinkDO.getId());
        orderDTO = CustomBeanUtil.copyProperties(order, new EnrolOrderDTO());
        orderDTO.setCustomer(customerDTO);
        orderDTO.setPayUrl(orderLinkDO.getUrl());
        // 支付状态
        orderDTO.setState(order.getState());
        // 支付方式 code ,ID 对应主数据不存在会进行错误提示
        orderDTO.setPaymentTypeCode(Objects.isNull(order.getPaymentTypeId()) ? null :
                Optional.of(paymentTypeApi.findById(order.getPaymentTypeId()))
                        .orElseThrow(BizExceptionProducer.produce(EnrolErrorBodyEnum.PAYMENT_TYPE_NOT_FUND,order.getPaymentTypeId())).getCode());
        // 订单头拓展信息
        LambdaQueryWrapper<OrderExtend> extendLambdaQueryWrapper = new LambdaQueryWrapper<OrderExtend>().in(OrderExtend::getOrderId, List.of(orderLinkDO.getId()));
        List<OrderExtend> extendList = orderExtendService.list(extendLambdaQueryWrapper);
        try {
            CustomBeanUtil.invokeMap(orderDTO, CollectionUtil.isEmpty(extendList) ? null : extendList.stream().collect(Collectors.toMap(OrderExtend::getName, OrderExtend::getValue)));
        } catch (IntrospectionException | NoSuchMethodException | InvocationTargetException |
                 InstantiationException | IllegalAccessException e) {
            BizExceptionProducer.throwProduce(e,EnrolErrorBodyEnum.ORDER_EXTEND_PROCESS_ERROR);
        }
        // 查询订单行
        List<EnrolClassOrderLineDTO> lines = this.getOrderLinesById(order.getId()) ;
        orderDTO.setOrderLineList(lines);
        // 目前短训 订单头和订单行一对一  ,  班级ID暂时放在订单头
        orderDTO.setClassId(CollectionUtil.isNotEmpty(lines) ? lines.get(0).getClassId() : null);
        orderDTO.setDiscountList(this.getDiscountListByIds(orderIds, DiscountScopeTypeEnum.NORMAL));
        orderDTO.setPersonalDiscountList(this.getDiscountListByIds(orderIds, DiscountScopeTypeEnum.PERSONAL));
        // 维护订单头原价格
        formDTO.setOrder(orderDTO);
        return formDTO;
    }

    private List<EnrolClassOrderLineDTO>  getOrderLinesById(String orderId) {
        // 查询订单行
        List<OrderLineDO> orderFormLineList = orderLineService.findByIdAndState(new OrderLineDO(orderId));
        Assert.isTrue(CollectionUtil.isNotEmpty(orderFormLineList), BizExceptionProducer.produce(EnrolErrorBodyEnum.ENROL_DETAIL_ORDER_NOT_FUND));
        return  orderFormLineList.stream().map(item -> {
            EnrolClassOrderLineDTO orderLineDTO = CustomBeanUtil.copyProperties(item, new EnrolClassOrderLineDTO());
            Product product = productMapper.selectById(item.getProductId());
            orderLineDTO.setClassId(ProductBeanUtil.getClazzId(product.getBizNo()));
            return orderLineDTO;
        }).collect(Collectors.toList());
    }

    private List<EnrolDiscountDTO> getDiscountListByIds(List<String> orderIds, DiscountScopeTypeEnum discountScopeType) {
        // 计算优惠金额 , 根据 associated_order_id 查询多阶段订单及对应定优惠列表
        QueryWrapper<OrderDiscount> discountQueryWrapper = new QueryWrapper<OrderDiscount>()
                .select("id, order_id ,order_line_id , discount_id ,discount_name," +
                        "sum(discount_amount) as discount_amount,max(sort) as sort,journal_discount_line_id")
                .eq("discount_scope_type", discountScopeType.name())
                .in("order_id", orderIds).groupBy("discount_id").orderByAsc("sort");
        List<OrderDiscount> discountList = orderDiscountMapper.selectList(discountQueryWrapper);
        // 根据多个阶段对接的订单  聚合查询后获取班级对应的优惠列表
        return CollectionUtil.isEmpty(discountList) ? null :
                discountList.stream()
                        .map(i -> new EnrolDiscountDTO(
                                i.getId(),
                                Objects.nonNull(i.getDiscountId()) ? Long.valueOf(i.getDiscountId()) : null,
                                i.getDiscountName(),
                                BigDecimal.valueOf(i.getDiscountAmount()).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP),
                                i.getJournalDiscountLineId(),
                                i.getSort()
                                )
                        )
                        .sorted()
                        .collect(Collectors.toList());
    }
}
